/**
 * 基于"react"的原生富文本编辑器
 * 其实只用了react的虚拟dom转真实dom的方法
 * 整体上不符合react的diff算法 后续改成组件的话 考虑把dom的算法优化一下
 * 功能复杂，具体表现为操作range光标和dom之间的来回切换，复制粘贴等操作的控制
 * by Wcl(Bogus)
 */
import React, { Component } from 'react'
import { Button, Col, message, Modal, Row, Spin, Tooltip, Tree, Select } from "antd";
import { messages } from "share/common";
import constants from "share/constants";
import ReactDOM from "react-dom";
import credentialService from "../credential-definition/credential-definition-service";
import 'styles/financial-account/calculator-textarea.scss';
import { SketchPicker } from 'react-color';
import plus from 'images/loan-billing/plus.svg'
import minus from 'images/loan-billing/minus.svg'
import color from 'images/loan-billing/color.svg'

const { TreeNode } = Tree;
const { Option } = Select;

class CalculatorTextArea extends Component {

  sel = null;

  range = null;

  state = {
    saveLoading: false,
    treeLoading: false,
    expandedKeys: [],
    treeList: [],
    childExpandedKeys: [],
    functionSegments: [],
    activeItem: {},
    activeId: '',
    fontValue: localStorage.getItem('calculator-size') || '14',
    displayColorPicker: false,
    colorValue: localStorage.getItem('calculator-color') || '#4E5B71',
    highLightId: ''
  };

  componentDidMount () {
    this.getTreeList();
  }

  componentWillReceiveProps = nextProps => {
    if (nextProps.visible) {
      if (!nextProps.infoData.isDcDirection)
        this.setState({ treeList: nextProps.treeList });
      setTimeout(() => {
        // this.refs.calRef && this.refs.calRef.focus();
        if (nextProps.value) {
          this.getFormulaJson();
        }
      }, 0);
    }
  };

  getFormulaJson = () => {
    const { infoData } = this.props;
    const { fontValue } = this.state;
    let obj = ReactDOM.findDOMNode(this.refs.calRef);
    credentialService.getTextarea({
      lineDefineId: infoData.lineDefineId,
      templateId: infoData.templateId,
      journalTemplateSceneId: infoData.journalTemplateSceneId ? infoData.journalTemplateSceneId : null
    }).then(res => {
      let { cellComment, segmentDTOs } = res.data;
      let childNodes = obj.childNodes;
      for (let i = childNodes.length - 1; i >= 0; i--) {
        obj.removeChild(childNodes[i]);
      }
      let textArray = this.getArray(cellComment);
      let el;
      textArray.forEach((item, index) => {
        if (typeof item === 'string') {
          el = document.createTextNode(item);
        } else {
          if (segmentDTOs.length && segmentDTOs[0].segmentName === item.value) {
            let style = {}, { directValue } = segmentDTOs[0];
            style.width = item.value.length * Number(fontValue) + 10 + 'px';
            let ele = React.createElement('input', {
              className: 'segment-tag',
              value: item.value,
              id: directValue,
              style: style,
              readOnly: true,
              onClick: () => this.handleFocus(directValue)
            });
            let frag = document.createDocumentFragment();
            segmentDTOs.splice(0, 1);
            el = ReactDOM.unstable_renderSubtreeIntoContainer(this, ele, frag);
          } else {
            el = document.createTextNode(`"${item.value}"`)
          }
        }
        obj.appendChild(el)
      });
      obj.blur();
      let { sel, range } = this;
      range = range.cloneRange();
      range.setStartAfter(el);
      range.collapse(true);
      sel.removeAllRanges();
      sel.addRange(range);
    }).catch(e => {
      message.error(e.response.data.message)
    })
  };

  getArray = (cellComment, arr = []) => {
    let judge = true;
    cellComment.replace(new RegExp('([.\n\\s\\S]*?)"([.\n\\s\\S]*?)"([.\n\\s\\S]*)','g'), (tar, $1, $2, $3) => {
      judge = false;
      $1 && arr.push($1);
      arr.push({
        type: 'segment',
        value: $2
      });
      if ($3) {
        return this.getArray($3, arr)
      } else {
        return arr;
      }
    });
    judge && arr.push(cellComment);
    return arr;
  };

  getTreeList = () => {
    const { infoData, treeList } = this.props;
    if (infoData.isDcDirection) {
      this.setState({ treeLoading: true });
      credentialService.getDebitTree({ setOfBooksId: infoData.setOfBooksId }).then(res => {
        let temp = [];
        temp.push({
          data: {
            detailSourceName: res.data.dcName,
            id: res.data.id,
            functionSegments: []
          }
        });
        res.data.defineDetailList.map(item => {
          temp[0].data.functionSegments.push({
            id: item.directionCode,
            segmentName: item.directionName
          });
        });
        this.setState({
          treeList: temp,
          treeLoading: false
        });
      }).catch(e => {
        this.setState({ treeLoading: false });
        message.error(e.response.data.message);
      })
    } else {
      this.setState({ treeList: treeList });
    }
  };

  loop = data => data.map(item => {
    if (item.data) {
      let node = item.data;
      let childNode = item.childNodes || [];
      return (
        <TreeNode
          key={node.id}
          title={(
            <div onClick={() => this.handleClickTitle(node)}>{node.detailSourceName}</div>
          )}
          ref={node.id}
        >
          {childNode && this.loop(childNode)}
        </TreeNode>
      )
    }
  });

  renderFunction = data => data.map(item => {
    const { highLightId } = this.state;
    if (item.childNodes && item.childNodes.length) {
      return (
        <TreeNode
          key={item.id}
          title={(
            <div
              key={item.id}
              ref={item.id}
              className={highLightId === item.id && 'high-light'}
              onClick={() => this.handleClickChildTitle(item)}
            >
              {item.segmentName}
            </div>
          )}
        >
          {item.childNodes && this.renderFunction(item.childNodes)}
        </TreeNode>
      )
    }
    return (
      <TreeNode
        key={item.id}
        title={(
          <div
            key={item.id}
            ref={item.id}
            className={highLightId === item.id && 'high-light'}
          >
            {item.segmentName}
          </div>
        )}
      />
    )
  });

  handleClickTitle = node => {
    this.addDBClickEvent(node.id);
    this.handleFindKey(node.id);
  };

  handleClickChildTitle = node => {
    this.childEvent(node.id);
    this.handleChildKey(node.id);
  };

  refAddEvent = (expandedKey, e) => {
    this.addDBClickEvent(e.node.props.eventKey);
    this.handleFindKey(e.node.props.eventKey);
  };

  handleTreeDetail = (expandedKey, e) => {
    this.childEvent(e.node.props.eventKey);
    this.handleChildKey(e.node.props.eventKey);
  };

  addDBClickEvent = (key, arrayList = this.state.treeList) => {
    const { activeId } = this.state;
    setTimeout(() => {
      arrayList.map(item => {
        let node = item.data;
        let childNode = item.childNodes;
        if (node) {
          childNode && this.addDBClickEvent(key, childNode);
          activeId !== key && node.functionSegments && this.addDBClickEvent(key, node.functionSegments);
        } else {
          if (window.addEventListener) {
            if (ReactDOM.findDOMNode(this.refs[item.id])) {
              ReactDOM.findDOMNode(this.refs[item.id]).addEventListener('dblclick', () => this.handleSegment(item));
              this.setState({ activeId: key });
            }
          } else {
            if (ReactDOM.findDOMNode(this.refs[item.id])) {
              ReactDOM.findDOMNode(this.refs[item.id]).attachEvent('ondblclick', () => this.handleSegment(item));
              this.setState({ activeId: key });
            }
          }
        }
      })
    }, 100)
  };

  childEvent = (key, arrayList = this.state.functionSegments) => {
    setTimeout(() => {
      arrayList.map(item => {
        if (item.id === key) {
          item.childNodes.map(record => {
            if (window.addEventListener) {
              if (ReactDOM.findDOMNode(this.refs[record.id])) {
                ReactDOM.findDOMNode(this.refs[record.id]).addEventListener('dblclick', () => this.handleSegment(record));
              }
            } else {
              if (ReactDOM.findDOMNode(this.refs[record.id])) {
                ReactDOM.findDOMNode(this.refs[record.id]).attachEvent('ondblclick', () => this.handleSegment(record));
              }
            }
          })
        }
      });
    }, 100)
  };

  handleFindKey = (key, arrayList = this.state.treeList) => {
    let { expandedKeys } = this.state;
    arrayList.map(item => {
      if (item.data.id === key) {
        let index = expandedKeys.indexOf(item.data.id);
        index > -1 ? expandedKeys.splice(index, 1) : expandedKeys.push(key);
        this.setState({ functionSegments: item.data.functionSegments, expandedKeys })
      } else {
        item.childNodes && this.handleFindKey(key, item.childNodes);
      }
    });
  };

  handleChildKey = (key, arrayList = this.state.functionSegments) => {
    const { childExpandedKeys } = this.state;
    arrayList.map(item => {
      if (item.id === key) {
        let index = childExpandedKeys.indexOf(item.id);
        index > -1 ? childExpandedKeys.splice(index, 1) : childExpandedKeys.push(key);
        this.setState({ childExpandedKeys })
      } else {
        item.childNodes && this.handleChildKey(key, item.childNodes);
      }
    });
  };

  handleBlur = () => {
    this.sel = window.getSelection();
    this.range = this.sel.getRangeAt(0);
  };

  renderSignOrFunc = isFunc => {
    const { activeItem } = this.state, { infoData } = this.props;
    let func = infoData.isCuringField ? constants.funcList.filter(item => item.id !== '5001') : constants.funcList;
    let temp = isFunc ? func : constants.signalList;
    temp = temp.map(item => (
      <div
        onClick={() => this.setState({ activeItem: item })}
        onDoubleClick={() => this.handleSignOrFunc(item)}
        className={`signal-single ${item.id === activeItem.id ? 'active' : ''}`}
      >
        {item.sign}
      </div>
    ));
    return temp;
  };

  handleSignOrFunc = item => {
    let { sel, range } = this;
    if (range) {
      let obj = ReactDOM.findDOMNode(this.refs.calRef);
      let childNodes = obj.childNodes;
      let startOffset = range.startOffset;
      let endOffset = range.endOffset;
      let el;
      if (childNodes.length === 0) {
        el = obj.appendChild(document.createTextNode(item.sign));
      } else {
        childNodes.forEach((node, index) => {
          if (node === range.startContainer || node === range.startContainer.parentNode) {
            let nodeText = node.textContent;
            let prefix = nodeText.substring(0, startOffset);
            let middle = item.sign + nodeText.substring(startOffset, endOffset);
            let suffix = nodeText.substring(endOffset, nodeText.length);
            obj.replaceChild(document.createTextNode(prefix + middle + suffix), node);
            el = obj.childNodes[index];
          }
        });
      }
      if (!el) {
        let fragment = document.createDocumentFragment();
        el = fragment.appendChild(document.createTextNode(item.sign));
        range.insertNode(fragment);
        range.setStartAfter(el);
        range.collapse(true);
      } else {
        range.setStart(el, startOffset + item.sign.length);
        range.setEnd(el, endOffset + item.sign.length);
      }
      sel.removeAllRanges();
      sel.addRange(range);
    }
  };

  handleSegment = item => {
    const { fontValue } = this.state;
    let style = {};
    style.width = item.segmentName.length * Number(fontValue) + 10 + 'px';
    this.insertNode(React.createElement('input', {
      className: 'segment-tag',
      value: item.segmentName,
      id: item.id,
      style: style,
      readOnly: true,
      onClick: () => this.handleFocus(item.id)
    }));
  };

  insertNode = ele => {
    let { sel, range } = this;
    if (sel) {
      if (range) {
        let frag = document.createDocumentFragment(), lastNode;
        lastNode = ReactDOM.unstable_renderSubtreeIntoContainer(this, ele, frag);
        range.insertNode(frag);
        if (lastNode) {
          range = range.cloneRange();
          range.setStartAfter(lastNode);
          range.collapse(true);
          sel.removeAllRanges();
          sel.addRange(range);
        }
      }
    }
  };

  handleFocus = id => {
    this.setState({ expandedKeys: [], childExpandedKeys: [], highLightId: id }, () => this.getHighLight(id))
  };

  getHighLight = (id, arr = this.state.treeList) => {
    let { expandedKeys } = this.state;
    arr.map(item => {
      if (JSON.stringify(item).indexOf(id) > -1) {
        expandedKeys.push(item.data.id);
        this.setState({ expandedKeys }, () => {
          if (JSON.stringify(item.data.functionSegments).indexOf(id) === -1) {
            item.childNodes && this.getHighLight(id, item.childNodes);
          } else {
            this.setState({ functionSegments: item.data.functionSegments }, () => {
              this.addDBClickEvent(item.data.id);
              this.handleChildExpanded(id);
            })
          }
        })
      }
    });
  };

  handleChildExpanded = (id, arr = this.state.functionSegments) => {
    let { childExpandedKeys } = this.state;
    arr.map(item => {
      if (JSON.stringify(item).indexOf(id) > -1) {
        if (item.id !== id) {
          childExpandedKeys.push(item.id);
          this.setState({ childExpandedKeys }, () => {
            this.handleChildExpanded(id, item.childNodes);
          });
        } else {
          const { childExpandedKeys } = this.state;
          this.childEvent(childExpandedKeys[childExpandedKeys.length - 1]);
        }
      }
    })
  };

  handleOk = () => {
    const { infoData, onOk } = this.props;
    let obj = ReactDOM.findDOMNode(this.refs.calRef);
    let cellComment = '';
    let segmentDTOs = [];
    obj.childNodes.forEach(item => {
      if (item.nodeName === '#text') {
        cellComment += item.data;
      }
      if (item.nodeName === 'INPUT') {
        cellComment += `"${item.value}"`;
        segmentDTOs.push({
          paramType: infoData.isDcDirection ? 8001 : 9000,
          directValue: item.id,
          segmentName: item.value
        })
      }
    });
    if (cellComment) {
      this.setState({ saveLoading: true });
      credentialService.saveTextarea({
        lineDefineId: infoData.lineDefineId,
        templateId: infoData.templateId,
        journalTemplateSceneId: infoData.journalTemplateSceneId ? infoData.journalTemplateSceneId : null,
        cellComment,
        segmentDTOs
      }).then(() => {
        this.setState({ saveLoading: false });
        onOk(cellComment);
      }).catch(e => {
        this.setState({ saveLoading: false });
        message.error(e.response.data.message);
      });
    }
  };

  handleChangeFontSize = value => {
    let fontValue = localStorage.getItem('calculator-size') || '14';
    if (typeof value === 'string') {
      fontValue = value;
    } else {
      fontValue = value ? Number(fontValue) + 2 : Number(fontValue) - 2;
      fontValue = fontValue > 20 ? '20' : (fontValue < '14' ? '14' : fontValue + '');
    }
    this.setState({ fontValue });
    localStorage.setItem('calculator-size', fontValue);
    Array.from(document.getElementsByClassName('segment-tag')).forEach(item => {
      item.setAttribute('style', `width: ${item.value.length * Number(fontValue) + 10}px`);
    });
  };

  handleChangeColor = color => {
    this.setState({ colorValue: color.hex });
    localStorage.setItem('calculator-color', color.hex);
  };

  handleChangeContent = () => {
    let obj = ReactDOM.findDOMNode(this.refs.calRef);
    let changed = false;
    obj.childNodes.forEach(node => {
      node.textContent.replace(new RegExp("([.\n\\s\\S]*?)'([.\n\\s\\S]*?)'([.\n\\s\\S]*)", 'g'), (tar, $1, $2, $3) => {
        let frag = document.createDocumentFragment();
        changed = true;
        frag.appendChild(document.createTextNode($1));
        let content = document.createElement('span');
        content.setAttribute('contenteditable', 'false');
        content.setAttribute('id', 'temp');
        content.setAttribute('style', 'padding: 0 3px');
        ReactDOM.unstable_renderSubtreeIntoContainer(this, <Select
          className={'code-select'}
          showSearch
          onSelect={this.handleSelectCode}
          onBlur={this.handleCodeBlur}
          getPopupContainer={() => ReactDOM.findDOMNode(this.refs.calRef)}
        >
          <Option key={'1'}>1</Option>
        </Select>, content);
        frag.appendChild(content);
        frag.appendChild(document.createTextNode($3));
        obj.replaceChild(frag, node);
      });
    });
    setTimeout(() => {
      changed && document.querySelector('.code-select').click();
    }, 0);
  };

  handleSelectCode = key => {
    let obj = ReactDOM.findDOMNode(this.refs.calRef);
    let { sel, range } = this;
    const { fontValue } = this.state;
    let style = {};
    style.width = 4 * Number(fontValue) + 10 + 'px';
    // TODO
    let ele = React.createElement('input', {
      className: 'segment-tag',
      value: '字段编码',
      id: 'boom',
      style: style,
      readOnly: true,
      onClick: () => this.handleFocus('boom')
    });
    let frag = document.createDocumentFragment(), lastNode;
    lastNode = ReactDOM.unstable_renderSubtreeIntoContainer(this, ele, frag);
    obj.replaceChild(frag, document.querySelector('#temp'));
    range.insertNode(frag);
    range = range.cloneRange();
    range.setStartAfter(lastNode);
    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
  };

  handleCodeBlur = () => {
    let obj = ReactDOM.findDOMNode(this.refs.calRef);
    let deleteNode = [];
    let tempNode = document.querySelector('#temp');
    tempNode && obj.removeChild(tempNode);
    obj.childNodes.forEach(node => {
      if (node.nodeName === 'DIV' || (node.nodeName === '#text' && node.data === '')) {
        deleteNode.push(node);
      }
    });
    deleteNode.forEach(node => obj.removeChild(node));
  };

  render () {
    const { visible, onCancel, onSwitch } = this.props;
    const {
      saveLoading, treeLoading, expandedKeys, treeList, childExpandedKeys, functionSegments, activeItem,
      fontValue, displayColorPicker, colorValue
    } = this.state;
    const styles = {
      popover: {
        position: 'absolute',
        lineHeight: '18px',
        zIndex: '2',
        boxSizing: 'content-box'
      },
      cover: {
        position: 'fixed',
        top: '0px',
        right: '0px',
        bottom: '0px',
        left: '0px',
      }
    };
    return (
      <div>
        <Modal
          visible={visible}
          onOk={this.handleOk}
          confirmLoading={saveLoading}
          onCancel={onCancel}
          className='calculator-textarea'
          width={1000}
          footer={
            <div>
              <Tooltip title={messages('finance-2.key258')/*切换旧版*/} placement='bottomLeft'>
                <Button
                  type='primary'
                  style={{ marginRight: 10 }}
                  onClick={onSwitch}
                  icon='switcher'
                />
              </Tooltip>
              <Button
                onClick={this.handleOk}
                type='primary'
                loading={saveLoading}
              >
                {messages('common.save')/*保存*/}
              </Button>
              <Button onClick={onCancel}>{messages('common.cancel')/*取消*/}</Button>
            </div>
          }
          title={messages('finance-2.key253')/*公式编辑器*/}
        >
          <div>
            <div
              style={{ width: "1000px", height: "600px", position: "fixed", zIndex: 999 }}
              onClick={e => e.stopPropagation()}
            />
            <div>
              <div className={'content-left'}>
                <div className='left-top'>
                  <Row>
                    <Col span={12} className='left-top-right'>
                      <div className='title'>{messages('finance-2.key254')/*取数*/}</div>
                      <div className='tree-content'>
                        <Spin spinning={treeLoading}>
                          <Tree onExpand={this.refAddEvent} expandedKeys={expandedKeys}>
                            {this.loop(treeList)}
                          </Tree>
                        </Spin>
                      </div>
                    </Col>
                    <Col span={12}>
                      <div className='title'>{messages('finance-2.key255')/*明细*/}</div>
                      <div className='tree-content'>
                        <Tree onExpand={this.handleTreeDetail} expandedKeys={childExpandedKeys}>
                          {this.renderFunction(functionSegments)}
                        </Tree>
                      </div>
                    </Col>
                  </Row>
                </div>
                <div className='left-bottom'>
                  <div className='title'>
                    {messages('finance-2.key259')/*公式*/}
                    <span className='title-span'>
                      {messages('finance-2.key260')/*(请按照说明输入运算符或函数，函数输入时注意大小写和符号)*/}
                    </span>
                    <div style={{ display: 'inline-block', float: 'right' }}>
                      <Select
                        styl={{ width: '60px' }}
                        onChange={value => this.handleChangeFontSize(value)}
                        value={fontValue}
                      >
                        <Option key={'12'}>12</Option>
                        <Option key={'14'}>14</Option>
                        <Option key={'16'}>16</Option>
                        <Option key={'18'}>18</Option>
                        <Option key={'20'}>20</Option>
                      </Select>
                      <img src={plus} style={{ width: '18px', cursor: 'pointer', margin: '0 8px' }}
                           onClick={() => this.handleChangeFontSize(true)}/>
                      <img src={minus} style={{ width: '16px', cursor: 'pointer' }}
                           onClick={() => this.handleChangeFontSize(false)}/>
                      <img src={color} style={{ width: '18px', cursor: 'pointer', marginLeft: '8px' }}
                           onClick={() => this.setState({ displayColorPicker: true })}/>
                      {
                        displayColorPicker && (
                          <div style={styles.popover}>
                            <div style={styles.cover} onClick={() => this.setState({ displayColorPicker: false })}/>
                            <SketchPicker color={colorValue} onChangeComplete={this.handleChangeColor}/>
                          </div>
                        )
                      }
                    </div>
                  </div>
                  <div
                    className='formula'
                    contentEditable
                    style={{
                      outline: 0,
                      fontSize: fontValue + 'px',
                      color: colorValue
                    }}
                    ref='calRef'
                    spellCheck={false}
                    onKeyUp={this.handleChangeContent}
                    onBlur={this.handleBlur}
                  />
                </div>
              </div>
              <div className={'content-right'}>
                <Row>
                  <Col span={10} className='content-right-right'>
                    <div className='title'>{messages('finance-2.key245')/*运算符*/}</div>
                    <div className='signal-all'>
                      {this.renderSignOrFunc(false)}
                    </div>
                  </Col>
                  <Col span={14}>
                    <div className='title'>{messages('finance-2.key246')/*函数*/}</div>
                    <div className='signal-all'>
                      {this.renderSignOrFunc(true)}
                    </div>
                  </Col>
                </Row>
                <div className='title'>{messages('finance-2.key256')/*解释说明：*/}</div>
                <div className='description'>
                  {activeItem.description}
                </div>
              </div>
            </div>
          </div>
        </Modal>
      </div>
    )
  }

}

export default CalculatorTextArea
